﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using Acid.Math;

namespace Acid.Graphics
{
    #region File Formats

    internal class File3ds
    {
        #region Supporting Structures

        public struct Triangle
        {
            public ushort vertex1;
            public ushort vertex2;
            public ushort vertex3;

            public Triangle(ushort v1, ushort v2, ushort v3)
            {
                vertex1 = v1;
                vertex2 = v2;
                vertex3 = v3;
            }

            public override string ToString()
            {
                return String.Format("v1: {0} v2: {1} v3: {2}", vertex1, vertex2, vertex3);
            }
        }


        public class Entity
        {
            // TODO: OO this
            // fields should be private
            // constructor with verts and faces
            // normalize in ctor

            // The stored vertices
            public Vector3[] vertices;

            // The calculated normals
            public Vector3[] normals;

            // The indices of the triangles which point to vertices
            public Triangle[] indices;

            // The coordinates which map the texture onto the entity
            public Vector2[] texcoords;



            public void CalculateNormals()
            {
                if (indices == null) return;

                normals = new Vector3[vertices.Length];

                Vector3[] temps = new Vector3[indices.Length];

                for (int ii = 0; ii < indices.Length; ii++)
                {
                    Triangle tr = indices[ii];
                    Vector3 v1 = vertices[tr.vertex1] - vertices[tr.vertex2];
                    Vector3 v2 = vertices[tr.vertex2] - vertices[tr.vertex3];
                    temps[ii] = Vector3.Cross(v1, v2);
                }

                for (int ii = 0; ii < vertices.Length; ii++)
                {
                    Vector3 v = new Vector3();
                    int shared = 0;

                    for (int jj = 0; jj < indices.Length; jj++)
                    {
                        Triangle tr = indices[jj];

                        if (tr.vertex1 == ii || tr.vertex2 == ii || tr.vertex3 == ii)
                        {
                            v += temps[jj];
                            shared++;
                        }
                    }

                    v.Normalize();

                    normals[ii] = v;
                }


            }
        }
        public enum Groups
        {
            C_PRIMARY = 0x4D4D,
            C_OBJECTINFO = 0x3D3D,
            C_VERSION = 0x0002,
            C_EDITKEYFRAME = 0xB000,
            C_MATERIAL = 0xAFFF,
            C_MATNAME = 0xA000,
            C_MATAMBIENT = 0xA010,
            C_MATDIFFUSE = 0xA020,
            C_MATSPECULAR = 0xA030,
            C_MATSHININESS = 0xA040,
            C_MATMAP = 0xA200,
            C_MATMAPFILE = 0xA300,
            C_OBJECT = 0x4000,
            C_OBJECT_MESH = 0x4100,
            C_OBJECT_VERTICES = 0x4110,
            C_OBJECT_FACES = 0x4120,
            C_OBJECT_MATERIAL = 0x4130,
            C_OBJECT_UV = 0x4140
        }
        public class Chunk3ds
        {
            public ushort ID;
            public uint Length;
            public int BytesRead;

            public Chunk3ds(BinaryReader reader)
            {
                // 2 byte ID
                ID = reader.ReadUInt16();

                // 4 byte length
                Length = reader.ReadUInt32();

                // = 6
                BytesRead = 6;
            }
        }
        #endregion

        BinaryReader reader;

        public List<Entity> entities = new List<Entity>();

        string base_dir;

        public File3ds(string file_name)
        {
            base_dir = new FileInfo(file_name).DirectoryName + "/";

            FileStream file = new FileStream(file_name, FileMode.Open, FileAccess.Read);

            reader = new BinaryReader(file);

            reader.BaseStream.Seek(0, SeekOrigin.Begin);

            Chunk3ds chunk = new Chunk3ds(reader);

            if (chunk.ID != (short)Groups.C_PRIMARY)
            {
                throw new ApplicationException("Not a proper 3DS file.");
            }

            ProcessChunk(chunk);

            reader.Close();

            file.Close();
        }

        void ProcessChunk(Chunk3ds chunk)
        {
            while (chunk.BytesRead < chunk.Length)
            {
                Chunk3ds child = new Chunk3ds(reader);

                switch ((Groups)child.ID)
                {
                    case Groups.C_VERSION:

                        int version = reader.ReadInt32();

                        child.BytesRead += 4;

                        break;

                    case Groups.C_OBJECTINFO:

                        Chunk3ds obj_chunk = new Chunk3ds(reader);

                        this.SkipChunk(obj_chunk);

                        child.BytesRead += obj_chunk.BytesRead;

                        ProcessChunk(child);

                        break;

                    case Groups.C_OBJECT:

                        string name = ProcessString(child);

                        Entity e = ProcessObjectChunk(child);


                        e.CalculateNormals();

                        this.entities.Add(e);

                        break;

                    default:

                        SkipChunk(child);

                        break;

                }

                chunk.BytesRead += child.BytesRead;
            }
        }

        int ProcessPercentageChunk(Chunk3ds chunk)
        {
            Chunk3ds child = new Chunk3ds(reader);

            int per = reader.ReadUInt16();

            child.BytesRead += 2;

            chunk.BytesRead += child.BytesRead;

            return per;
        }

        Entity ProcessObjectChunk(Chunk3ds chunk)
        {
            return ProcessObjectChunk(chunk, new Entity());
        }

        Entity ProcessObjectChunk(Chunk3ds chunk, Entity e)
        {
            while (chunk.BytesRead < chunk.Length)
            {
                Chunk3ds child = new Chunk3ds(reader);

                switch ((Groups)child.ID)
                {
                    case Groups.C_OBJECT_MESH:

                        ProcessObjectChunk(child, e);

                        break;

                    case Groups.C_OBJECT_VERTICES:

                        e.vertices = ReadVertices(child);

                        break;

                    case Groups.C_OBJECT_FACES:

                        e.indices = ReadIndices(child);

                        if (child.BytesRead < child.Length)
                        {
                            ProcessObjectChunk(child, e);
                        }

                        break;

                    case Groups.C_OBJECT_UV:

                        int count = reader.ReadUInt16();

                        child.BytesRead += 2;

                        e.texcoords = new Vector2[count];

                        for (int ii = 0; ii < count; ii++)
                        {
                            e.texcoords[ii] = new Vector2(reader.ReadSingle(), reader.ReadSingle());
                        }

                        child.BytesRead += (count * (4 * 2));

                        break;

                    default:

                        this.SkipChunk(child);

                        break;
                }

                chunk.BytesRead += child.BytesRead;
            }

            return e;
        }

        void SkipChunk(Chunk3ds chunk)
        {
            int length = (int)chunk.Length - chunk.BytesRead;

            reader.ReadBytes(length);

            chunk.BytesRead += length;
        }

        string ProcessString(Chunk3ds chunk)
        {
            StringBuilder sb = new StringBuilder();

            byte b = reader.ReadByte();

            int idx = 0;

            while (b != 0)
            {
                sb.Append((char)b);

                b = reader.ReadByte();

                idx++;
            }
            chunk.BytesRead += idx + 1;

            return sb.ToString();
        }

        Vector3[] ReadVertices(Chunk3ds chunk)
        {
            ushort numVerts = reader.ReadUInt16();

            chunk.BytesRead += 2;

            Console.WriteLine(" Vertices: {0}", numVerts);

            Vector3[] verts = new Vector3[numVerts];

            for (int ii = 0; ii < verts.Length; ii++)
            {
                float f1 = reader.ReadSingle();

                float f2 = reader.ReadSingle();

                float f3 = reader.ReadSingle();

                verts[ii] = new Vector3(f1, f3, -f2);
            }



            chunk.BytesRead += verts.Length * (3 * 4);

            return verts;
        }

        Triangle[] ReadIndices(Chunk3ds chunk)
        {
            ushort numIdcs = reader.ReadUInt16();

            chunk.BytesRead += 2;

            Console.WriteLine(" Indices: {0}", numIdcs);

            Triangle[] idcs = new Triangle[numIdcs];

            for (int ii = 0; ii < idcs.Length; ii++)
            {
                idcs[ii] = new Triangle(reader.ReadUInt16(), reader.ReadUInt16(), reader.ReadUInt16());

                reader.ReadUInt16();
            }
            chunk.BytesRead += (2 * 4) * idcs.Length;

            return idcs;
        }
    }

    #endregion

    public class MeshLoader
    {
        #region Mesh Input

        public static Mesh From3ds(Device device, string filename)
        {
            Mesh mesh = new Mesh(device);

            File3ds File3ds = new File3ds(filename);

            List<Mesh.Vertex> vertices = new List<Mesh.Vertex>();

            ushort count = 0;

            for (int i = 0; i < File3ds.entities.Count; i++)
            {
                File3ds.Entity entity = File3ds.entities[i];

                entity.CalculateNormals();

                if (entity.indices == null)
                {
                    // Could move this into a model load output result string

                    //System.Windows.Forms.MessageBox.Show("failed");
                }
                if (entity.indices != null)
                {
                    List<ushort> indices = new List<ushort>();


                    for (int j = 0; j < entity.indices.Length; j++)
                    {
                        indices.Add((ushort)(entity.indices[j].vertex1 + count));

                        indices.Add((ushort)(entity.indices[j].vertex2 + count));

                        indices.Add((ushort)(entity.indices[j].vertex3 + count));
                    }

                    IndexBuffer ib = new IndexBuffer();

                    ib.SetData(indices.ToArray());

                    mesh.IndexBuffers.Add(ib);

                    count += (ushort)entity.vertices.Length;
                    //////////////////////
                    // Unpack Vertices
                    //////////////////////
                    for (int j = 0; j < entity.vertices.Length; j++)
                    {
                        if (entity.texcoords != null)
                        {
                            vertices.Add(new Mesh.Vertex(entity.vertices[j].X,
                                                         entity.vertices[j].Y,
                                                         entity.vertices[j].Z,
                                                         entity.normals[j].X,
                                                         entity.normals[j].Y,
                                                         entity.normals[j].Z,
                                                         entity.texcoords[j].X,
                                                         entity.texcoords[j].Y));
                        }
                        else
                        {
                            vertices.Add(new Mesh.Vertex(entity.vertices[j].X,
                                                         entity.vertices[j].Y,
                                                         entity.vertices[j].Z,
                                                         entity.normals[j].X,
                                                         entity.normals[j].Y,
                                                         entity.normals[j].Z,
                                                         0.0f,
                                                         0.0f));
                        }
                    }
                }
            }
            mesh.VertexBuffer.SetData(vertices.ToArray());

            return mesh;
        }

        public static Mesh Empty(Device device)
        {
            Mesh mesh = new Mesh(device);

            mesh.VertexBuffer = new VertexBuffer(Mesh.Vertex.Declaration);

            mesh.IndexBuffers = new List<IndexBuffer>();

            return mesh;
        }

        public static Mesh GpuQuad(Device device)
        {
            return GpuQuad(device, false);
        }

        public static Mesh GpuQuad(Device device, bool flipx)
        {
            Mesh mesh = new Mesh(device);
            if (!flipx)
            {

                mesh = new Mesh(device);
                mesh.IndexBuffers = new List<IndexBuffer>();
                mesh.IndexBuffers.Add(new IndexBuffer());
                mesh.IndexBuffers[0].SetData(new ushort[] { 0, 1, 2, 2, 3, 0 });
                mesh.VertexBuffer.SetData(new Mesh.Vertex[] {new Mesh.Vertex(-0.5f, -0.5f, 0.0f, 1.0f, 0.0f),
                                                            new Mesh.Vertex(-0.5f,  0.5f, 0.0f, 1.0f, 1.0f),
                                                            new Mesh.Vertex( 0.5f,  0.5f, 0.0f, 0.0f, 1.0f),
                                                            new Mesh.Vertex( 0.5f, -0.5f, 0.0f, 0.0f, 0.0f) });
            }
            else
            {
                mesh = new Mesh(device);
                mesh.IndexBuffers = new List<IndexBuffer>();
                mesh.IndexBuffers.Add(new IndexBuffer());
                mesh.IndexBuffers[0].SetData(new ushort[] { 0, 1, 2, 2, 3, 0 });
                mesh.VertexBuffer.SetData(new Mesh.Vertex[] { new Mesh.Vertex(-0.5f, -0.5f, 0.0f, 0.0f, 0.0f),
                                                              new Mesh.Vertex(-0.5f,  0.5f, 0.0f, 0.0f, 1.0f),
                                                              new Mesh.Vertex( 0.5f,  0.5f, 0.0f, 1.0f, 1.0f),
                                                              new Mesh.Vertex( 0.5f, -0.5f, 0.0f, 1.0f, 0.0f) });
            }
            return mesh;
        }

        public static Mesh Box(Device device, float width, float height, float depth)
        {
            Mesh mesh = new Mesh(device);

            mesh.IndexBuffers.Add(new IndexBuffer());

            width = width / 2;
            height = height / 2;
            depth = depth / 2;

            Mesh.Vertex[] vertices = new Mesh.Vertex[24];
            // X
            vertices[0] = new Mesh.Vertex(-width, height, depth, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
            vertices[1] = new Mesh.Vertex(-width, -height, depth, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
            vertices[2] = new Mesh.Vertex(-width, -height, -depth, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
            vertices[3] = new Mesh.Vertex(-width, height, -depth, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);

            vertices[4] = new Mesh.Vertex(width, height, -depth, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
            vertices[5] = new Mesh.Vertex(width, -height, -depth, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
            vertices[6] = new Mesh.Vertex(width, -height, depth, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
            vertices[7] = new Mesh.Vertex(width, height, depth, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
            // Z
            vertices[8] = new Mesh.Vertex(width, -height, -depth, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
            vertices[9] = new Mesh.Vertex(-width, -height, -depth, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
            vertices[10] = new Mesh.Vertex(-width, height, -depth, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);
            vertices[11] = new Mesh.Vertex(width, height, -depth, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);

            vertices[12] = new Mesh.Vertex(width, height, depth, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);
            vertices[13] = new Mesh.Vertex(width, -height, depth, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);
            vertices[14] = new Mesh.Vertex(-width, -height, depth, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
            vertices[15] = new Mesh.Vertex(-width, height, depth, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f);
            // Y
            vertices[16] = new Mesh.Vertex(-width, height, depth, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f);
            vertices[17] = new Mesh.Vertex(-width, height, -depth, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
            vertices[18] = new Mesh.Vertex(width, height, -depth, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
            vertices[19] = new Mesh.Vertex(width, height, depth, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f);


            vertices[20] = new Mesh.Vertex(-width, -height, -depth, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f);
            vertices[21] = new Mesh.Vertex(-width, -height, depth, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f);
            vertices[22] = new Mesh.Vertex(width, -height, depth, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f);
            vertices[23] = new Mesh.Vertex(width, -height, -depth, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f);
            mesh.VertexBuffer.SetData(vertices);

            ushort[] indices = new ushort[36];
            indices[0] = 0;
            indices[1] = 3;
            indices[2] = 2;
            indices[3] = 2;
            indices[4] = 1;
            indices[5] = 0;

            indices[6] = 4;
            indices[7] = 7;
            indices[8] = 6;
            indices[9] = 6;
            indices[10] = 5;
            indices[11] = 4;

            indices[12] = 8;
            indices[13] = 9;
            indices[14] = 10;
            indices[15] = 10;
            indices[16] = 11;
            indices[17] = 8;

            indices[18] = 12;
            indices[19] = 15;
            indices[20] = 14;
            indices[21] = 14;
            indices[22] = 13;
            indices[23] = 12;

            indices[24] = 16;
            indices[25] = 19;
            indices[26] = 18;
            indices[27] = 18;
            indices[28] = 17;
            indices[29] = 16;

            indices[30] = 20;
            indices[31] = 23;
            indices[32] = 22;
            indices[33] = 22;
            indices[34] = 21;
            indices[35] = 20;

            mesh.IndexBuffers[0].SetData(indices);

            return mesh;
        }

        #endregion

    
    }
}
